
#include <stdio.h>
#include <string.h>
#include <assert.h>

#include "string.h"
#include "raise.h"
#include "native_array.h"
#include "new.h"

#define INITIAL_SIZE 16

BEGIN_CLASS(NativeArray);
    char* _buf;
    size_t _buf_size;
    size_t _count;
    size_t _size;
    char* _str;
END_CLASS(NativeArray);

static void NativeArray_resize(NativeArrayClass* this, size_t size)
{
    if (this->_buf_size > size)
        return;

    if (size - this->_buf_size < INITIAL_SIZE)
        size += INITIAL_SIZE;
    if (this->_buf == NULL)
    {
        this->_buf = calloc(size, this->_size);
        if (this->_buf == NULL)
            raise("Out of memory");
    }
    else
    {
        this->_buf = realloc(this->_buf, this->_size * size);
        if (this->_buf == NULL)
            raise("Out of memory");
        memset(this->_buf + this->_buf_size * this->_size, 0, (size - this->_buf_size) * this->_size);
    }
    this->_buf_size = size;
}

static void NativeArray_ctor(NativeArrayClass* this, va_list* args)
{
    this->_count = va_arg(*args, size_t);
    this->_size = va_arg(*args, size_t);
#ifndef NDEBUG
    switch (this->_size)
    {
    case 1:
    case 2:
    case 4:
    case 8:
        break;
    default:
        raise("Native size not implemented");
    }
#endif
    if (this->_count > 0 && this->_count < INITIAL_SIZE)
        this->_buf_size = INITIAL_SIZE;
    else
        this->_buf_size = this->_count;
    if (this->_count > 0 && (this->_buf = calloc(this->_buf_size, this->_size)) == NULL)
        raise("Out of memory");
}

static void NativeArray_dtor(NativeArrayClass* this)
{
    free(this->_str);
    free(this->_buf);
}

static char const* NativeArray_str(NativeArrayClass* this)
{
    if (this->_str != NULL)
        return this->_str;
    size_t str_size = this->_count * this->_size,
           pos;
    if ((this->_str = malloc(str_size)) == NULL)
        raise("Out of memory");
    assert(str_size > 3);
    this->_str[0] = '[';
    this->_str[1] = '\0';
    pos = 1;

    for (size_t i = 0; i < this->_count; ++i)
    {
        int res;
# define cast_cur_to(type) *(type*)(this->_buf + i * this->_size)
# define check_str_size(sz) \
do { \
    if (str_size - pos < ((size_t) sz) + 1) \
    { \
        str_size += (((size_t) sz) + 1) * 1.2; \
        if ((this->_str = realloc(this->_str, str_size)) == NULL) \
            raise("Out of memory"); \
    } \
} while(0)

# define print_cur(fmt, type) \
do {\
    res = snprintf(&this->_str[pos], str_size - pos, fmt, cast_cur_to(type)); \
    while (res < 0 || ((size_t) res) >= str_size - pos) \
    { \
        check_str_size((res > 0 ? res + 1 : 16)); \
        res = snprintf(&this->_str[pos], str_size - pos, fmt, cast_cur_to(type)); \
    } \
    pos += res; \
} while (0)

        if (i > 0)
        {
            check_str_size(3);
            this->_str[pos++] = ',';
            this->_str[pos++] = ' ';
            this->_str[pos] = '\0';
        }
        if (this->_size == 1)
            print_cur("%c", int8_t);
        else if (this->_size == 2)
            print_cur("%hi", int16_t);
        else if (this->_size == 4)
            print_cur("%d", int32_t);
        else if (this->_size == 8)
            print_cur("%lld", int64_t);
    }
    check_str_size(3);
    this->_str[pos++] = ']';
    this->_str[pos] = '\0';

#undef print_cur
#undef check_str_size
#undef cast_cur_to

    return this->_str;
}

static Object* NativeArray_add(NativeArrayClass const* this, NativeArrayClass const* other)
{
    assert(this != NULL && "this is NULL");
    assert(other != NULL && "other is NULL");
    assert(this->_size == other->_size && "Incompatible array");
    NativeArrayClass* res = new(NativeArray, this->_count, this->_size + other->_size);
    if (res->_size == 0)
        return res;
    memcpy(res->_buf, this->_buf, this->_size * this->_count);
    memcpy(res->_buf + this->_size * this->_count, other->_buf, other->_size * other->_count);
    return res;
}

static char const* NativeArray_front(NativeArrayClass const* this)
{
    if (this->_count == 0)
        raise("front(): NativeArray is empty");
    return &(this->_buf[0]);
}

static char const* NativeArray_back(NativeArrayClass const* this)
{
    if (this->_count == 0)
        raise("front(): NativeArray is empty");
    return &(this->_buf[(this->_count - 1) * this->_size]);
}

static void    NativeArray_push_back(NativeArrayClass* this, va_list* ap)
{
    size_t index = (this->_count == 0 ? 0 : this->_count);
    this->_count = index + 1;
    if (this->_count >= this->_buf_size)
        NativeArray_resize(this, this->_count);
    switch (this->_size)
    {
    case 1:
        this->_buf[index * this->_size] = va_arg(*ap, int);
        break;
    case 2:
        *((int16_t*) &this->_buf[index * this->_size]) = va_arg(*ap, int);
        break;
    case 4:
        *((int32_t*) &this->_buf[index * this->_size]) = va_arg(*ap, int);
        break;
    case 8:
        *((int64_t*) &this->_buf[index * this->_size]) = va_arg(*ap, int64_t);
        break;
    default:
        raise("Native size not implemented");
    }
}

static size_t NativeArray_size(NativeArrayClass* this)
{
    return this->_count;
}

static void NativeArray_setitem(NativeArrayClass* this, va_list* ap)
{
    size_t index = va_arg(*ap, size_t);
    if (index >= this->_count)
    {
        this->_count = index + 1;
        if (this->_count >= this->_buf_size)
             NativeArray_resize(this, this->_count + INITIAL_SIZE);
    }

    switch (this->_size)
    {
    case 1:
        this->_buf[index * this->_size] = va_arg(*ap, int);
        break;
    case 2:
        *((int16_t*) &this->_buf[index * this->_size]) = va_arg(*ap, int);
        break;
    case 4:
        *((int32_t*) &this->_buf[index * this->_size]) = va_arg(*ap, int);
        break;
    case 8:
        *((int64_t*) &this->_buf[index * this->_size]) = va_arg(*ap, int64_t);
        break;
    default:
        raise("Native size not implemented");
    }
}

static char const* NativeArray_getitem(NativeArrayClass const* this, va_list* ap)
{
    size_t index = va_arg(*ap, size_t);
    if (index >= this->_count)
        raise("Out of range");
    return &(this->_buf[index * this->_size]);
}

static Object* NativeArray_begin(Object* this)
{
    (void) this;
    raise("Not implemented");
    return NULL;
}

static Object* NativeArray_end(Object* this)
{
    (void) this;
    raise("Not implemented");
    return NULL;
}


IMPL_CONTAINER_CLASS(
    NativeArray,
    &NativeArray_ctor, &NativeArray_dtor, &NativeArray_str,
    &NativeArray_add, NULL, NULL, NULL,
    NULL, NULL, NULL, // eq, gt, lt
    NULL, //push_front
    &NativeArray_push_back,
    NULL, //pop_front
    NULL, //pop_back
    NULL, //append
    NULL, //erase
    NULL, //clear
    &NativeArray_front,
    &NativeArray_back,
    &NativeArray_size,
    &NativeArray_setitem,
    &NativeArray_getitem,
    &NativeArray_begin,
    &NativeArray_end,
    NULL,   // _buf
    0,      // _buf_size
    0,      // _count
    0,      // _size
    NULL    // _str
);



